package com.coolw.spring.statemachine.support;

import com.coolw.common.exception.ExceptionUtil;
import com.coolw.spring.statemachine.constant.StateMachineConst;
import com.coolw.spring.statemachine.enums.MachineIdEnum;
import com.coolw.spring.statemachine.util.StateMachineUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.text.StrUtil;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.persist.StateMachinePersister;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * StateMachineHolder
 *
 * @author coolw
 * @date 2024/4/16 15:00
 */
@Slf4j
@Component
public class StateMachineHolder<S extends BaseState, E extends BaseEvent, C extends BaseContext, T> {

    private final Map<String, AbstractStateMachineBuilder<S, E>> builderMap;

    @Resource
    private BeanFactory beanFactory;

    @Resource
    private StateMachine<S, E> orderStateMachine;

    @Resource
    private StateMachinePersister<S, E, T> stateMachineMemPersister;

    public StateMachineHolder(List<AbstractStateMachineBuilder<S, E>> builderList) {
        builderMap = new HashMap<>(builderList.size());
        builderList.forEach(b -> builderMap.put(b.getMachineId().getCode(), b));
    }

    public StateMachine<S, E> getStateMachine(MachineIdEnum machineId) {
        try {
            AbstractStateMachineBuilder<S, E> builder = builderMap.get(machineId.getCode());
            return builder.builder(beanFactory);
        } catch (Exception e) {
            log.error("getStateMachine error", e);
            return null;
        }
    }

    /**
     * 发送状态转换事件(线程安全)
     *
     * @param event     事件
     * @param context   上下文
     * @param bizId     唯一的业务ID
     * @param machineId 商户ID
     * @return 处理结果
     */
    public boolean sendEvent(E event, C context, T bizId, MachineIdEnum machineId) {
        StateMachine<S, E> stateMachine = getStateMachine(machineId);
        Assert.notNull(stateMachine, StrUtil.format("[{}]未匹配到状态机", machineId.getCode()));

        boolean result;
        try {
            orderStateMachine.start();

            // 尝试恢复状态机状态
            stateMachineMemPersister.restore(orderStateMachine, bizId);

            Message<E> message = MessageBuilder.withPayload(event)
                    .setHeader(StateMachineConst.CONTEXT_KEY, context)
                    .build();

            // 触发事件
            result = orderStateMachine.sendEvent(message);

            // 检查执行结果
            StateMachineUtil.checkActionError(orderStateMachine);

            // 持久化状态机信息
            stateMachineMemPersister.persist(orderStateMachine, bizId);
        } catch (Exception e) {
            log.error("send event error,bizId:{},event:{}, context:{}", bizId, e, context, e);
            throw ExceptionUtil.createBusinessException("send event error", e);
        }
        return result;
    }
}
